在前一天的進度中,我們已經完成了 基本格子繪製,能夠看到遊戲畫面上整齊的網格,並且維持未揭開狀態。今天我們要進一步實作 點擊揭開格子,並處理經典踩地雷中的一個重要功能:空白區域的自動擴散(Flood Fill)。
玩家點擊某個格子時:
這裡的「自動展開」其實就是 Flood Fill 演算法,和影像處理中的「顏色填滿」非常類似。
以下是簡化過的 Ebiten 程式碼,示範如何實作點擊揭開與 Flood Fill:
func (g *GameLayout) Update() error {
// 偵測 mouse click 事件
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
xPos, yPos := ebiten.CursorPosition()
row := yPos / gridSize
col := xPos / gridSize
if row >= 0 && row < Rows && col >= 0 && col < Cols {
// 執行 Flood Fill
g.gameInstance.Board.Reveal(row, col)
}
}
return nil
}
func (g *GameLayout) Draw(screen *ebiten.Image) {
for row := 0; row < Rows; row++ {
for col := 0; col < Cols; col++ {
// 取出格子狀態
cell := g.gameInstance.Board.GetCell(row, col)
// 根據格子狀態,顯示對應的畫面
// 當格子沒有被掀開時,畫出原本的灰階
if !cell.Revealed {
g.drawUnTouchCell(screen, row, col)
} else {
g.drawTouchCellBackground(screen, row, col)
if cell.AdjacenetMines != 0 {
g.drawTouchCellAdjacency(screen, row, col, cell.AdjacenetMines)
}
if cell.IsMine {
g.drawTouchCellMine(screen, row, col)
}
}
}
}
}
// Reveal - 從 row, col 開始翻開周圍不是地雷,直到遇到非零的格子
func (board *Board) Reveal(row, col int) {
// 超出邊界
if row < 0 || row >= board.rows ||
col < 0 || col >= board.cols {
return
}
cell := board.cells[row][col]
// 已經被揭開
if cell.Revealed {
return
}
// 標注該格已經被揭開
board.cells[row][col].Revealed = true
// 如果是空白格 (AdjacenetMines = 0, 且不是地雷)
if !cell.IsMine && cell.AdjacenetMines == 0 {
// 鄰近所有方向
neighborDirections := [8]coord{
{Row: -1, Col: -1}, {Row: -1, Col: 0}, {Row: -1, Col: 1},
{Row: 0, Col: -1}, {Row: 0, Col: 1},
{Row: 1, Col: -1}, {Row: 1, Col: 0}, {Row: 1, Col: 1},
}
for _, direction := range neighborDirections {
neighborRow, neighborCol := row+direction.Row, col+direction.Col
board.Reveal(neighborRow, neighborCol)
}
}
}
從按下的位置的格子開始
step1 首先檢查當下這個格子是否已經走訪過了
step2 當已經走訪過,則直接結束
step3 如果還沒走訪過,則標注為走否過
step4 如果這個格子是地雷則結束
step5 如果這個格子鄰近的地雷數超過 0 則結束
step6 從這個格子以鄰近的格子為當下位置 從 step1 開始執行
點擊格子後:
明天我們將要實作 右鍵插旗功能,讓玩家可以標記地雷位置,進一步接近完整的踩地雷玩法。